/*! 
 * jquery.event.drop - v 2.2
 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
 * Open Source MIT License - http://threedubmedia.com/code/license
 */
// Created: 2008-06-04 
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x, event.drag 2.2

;
(function ($) { // secure $ jQuery alias

// Events: drop, dropstart, dropend

// add the jquery instance method
  $.fn.drop = function (str, arg, opts) {
    // figure out the event type
    var type = typeof str == "string" ? str : "",
    // figure out the event handler...
      fn = $.isFunction(str) ? str : $.isFunction(arg) ? arg : null;
    // fix the event type
    if (type.indexOf("drop") !== 0)
      type = "drop" + type;
    // were options passed
    opts = ( str == fn ? arg : opts ) || {};
    // trigger or bind event handler
    return fn ? this.bind(type, opts, fn) : this.trigger(type);
  };

// DROP MANAGEMENT UTILITY
// returns filtered drop target elements, caches their positions
  $.drop = function (opts) {
    opts = opts || {};
    // safely set new options...
    drop.multi = opts.multi === true ? Infinity :
        opts.multi === false ? 1 : !isNaN(opts.multi) ? opts.multi : drop.multi;
    drop.delay = opts.delay || drop.delay;
    drop.tolerance = $.isFunction(opts.tolerance) ? opts.tolerance :
        opts.tolerance === null ? null : drop.tolerance;
    drop.mode = opts.mode || drop.mode || 'intersect';
  };

// local refs (increase compression)
  var $event = $.event,
    $special = $event.special,
// configure the drop special event
    drop = $.event.special.drop = {

      // these are the default settings
      multi: 1, // allow multiple drop winners per dragged element
      delay: 20, // async timeout delay
      mode: 'overlap', // drop tolerance mode

      // internal cache
      targets: [],

      // the key name for stored drop data
      datakey: "dropdata",

      // prevent bubbling for better performance
      noBubble: true,

      // count bound related events
      add: function (obj) {
        // read the interaction data
        var data = $.data(this, drop.datakey);
        // count another realted event
        data.related += 1;
      },

      // forget unbound related events
      remove: function () {
        $.data(this, drop.datakey).related -= 1;
      },

      // configure the interactions
      setup: function () {
        // check for related events
        if ($.data(this, drop.datakey))
          return;
        // initialize the drop element data
        var data = {
          related: 0,
          active: [],
          anyactive: 0,
          winner: 0,
          location: {}
        };
        // store the drop data on the element
        $.data(this, drop.datakey, data);
        // store the drop target in internal cache
        drop.targets.push(this);
      },

      // destroy the configure interaction
      teardown: function () {
        var data = $.data(this, drop.datakey) || {};
        // check for related events
        if (data.related)
          return;
        // remove the stored data
        $.removeData(this, drop.datakey);
        // reference the targeted element
        var element = this;
        // remove from the internal cache
        drop.targets = $.grep(drop.targets, function (target) {
          return ( target !== element );
        });
      },

      // shared event handler
      handler: function (event, dd) {
        // local vars
        var results, $targets;
        // make sure the right data is available
        if (!dd)
          return;
        // handle various events
        switch (event.type) {
          // draginit, from $.event.special.drag
          case 'mousedown': // DROPINIT >>
          case 'touchstart': // DROPINIT >>
            // collect and assign the drop targets
            $targets = $(drop.targets);
            if (typeof dd.drop == "string")
              $targets = $targets.filter(dd.drop);
            // reset drop data winner properties
            $targets.each(function () {
              var data = $.data(this, drop.datakey);
              data.active = [];
              data.anyactive = 0;
              data.winner = 0;
            });
            // set available target elements
            dd.droppable = $targets;
            // activate drop targets for the initial element being dragged
            $special.drag.hijack(event, "dropinit", dd);
            break;
          // drag, from $.event.special.drag
          case 'mousemove': // TOLERATE >>
          case 'touchmove': // TOLERATE >>
            drop.event = event; // store the mousemove event
            if (!drop.timer)
            // monitor drop targets
              drop.tolerate(dd);
            break;
          // dragend, from $.event.special.drag
          case 'mouseup': // DROP >> DROPEND >>
          case 'touchend': // DROP >> DROPEND >>
            drop.timer = clearTimeout(drop.timer); // delete timer
            if (dd.propagates) {
              $special.drag.hijack(event, "drop", dd);
              $special.drag.hijack(event, "dropend", dd);
            }
            break;

        }
      },

      // returns the location positions of an element
      locate: function (elem, index) {
        var data = $.data(elem, drop.datakey),
          $elem = $(elem),
          posi = $elem.offset() || {},
          height = $elem.outerHeight(),
          width = $elem.outerWidth(),
          location = {
            elem: elem,
            width: width,
            height: height,
            top: posi.top,
            left: posi.left,
            right: posi.left + width,
            bottom: posi.top + height
          };
        // drag elements might not have dropdata
        if (data) {
          data.location = location;
          data.index = index;
          data.elem = elem;
        }
        return location;
      },

      // test the location positions of an element against another OR an X,Y coord
      contains: function (target, test) { // target { location } contains test [x,y] or { location }
        return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
          && ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom );
      },

      // stored tolerance modes
      modes: { // fn scope: "$.event.special.drop" object
        // target with mouse wins, else target with most overlap wins
        'intersect': function (event, proxy, target) {
          return this.contains(target, [ event.pageX, event.pageY ]) ? // check cursor
            1e9 : this.modes.overlap.apply(this, arguments); // check overlap
        },
        // target with most overlap wins
        'overlap': function (event, proxy, target) {
          // calculate the area of overlap...
          return Math.max(0, Math.min(target.bottom, proxy.bottom) - Math.max(target.top, proxy.top))
            * Math.max(0, Math.min(target.right, proxy.right) - Math.max(target.left, proxy.left));
        },
        // proxy is completely contained within target bounds
        'fit': function (event, proxy, target) {
          return this.contains(target, proxy) ? 1 : 0;
        },
        // center of the proxy is contained within target bounds
        'middle': function (event, proxy, target) {
          return this.contains(target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ]) ? 1 : 0;
        }
      },

      // sort drop target cache by by winner (dsc), then index (asc)
      sort: function (a, b) {
        return ( b.winner - a.winner ) || ( a.index - b.index );
      },

      // async, recursive tolerance execution
      tolerate: function (dd) {
        // declare local refs
        var i, drp, drg, data, arr, len, elem,
        // interaction iteration variables
          x = 0, ia, end = dd.interactions.length,
        // determine the mouse coords
          xy = [ drop.event.pageX, drop.event.pageY ],
        // custom or stored tolerance fn
          tolerance = drop.tolerance || drop.modes[ drop.mode ];
        // go through each passed interaction...
        do if (ia = dd.interactions[x]) {
          // check valid interaction
          if (!ia)
            return;
          // initialize or clear the drop data
          ia.drop = [];
          // holds the drop elements
          arr = [];
          len = ia.droppable.length;
          // determine the proxy location, if needed
          if (tolerance)
            drg = drop.locate(ia.proxy);
          // reset the loop
          i = 0;
          // loop each stored drop target
          do if (elem = ia.droppable[i]) {
            data = $.data(elem, drop.datakey);
            drp = data.location;
            if (!drp) continue;
            // find a winner: tolerance function is defined, call it
            data.winner = tolerance ? tolerance.call(drop, drop.event, drg, drp)
              // mouse position is always the fallback
              : drop.contains(drp, xy) ? 1 : 0;
            arr.push(data);
          } while (++i < len); // loop
          // sort the drop targets
          arr.sort(drop.sort);
          // reset the loop
          i = 0;
          // loop through all of the targets again
          do if (data = arr[ i ]) {
            // winners...
            if (data.winner && ia.drop.length < drop.multi) {
              // new winner... dropstart
              if (!data.active[x] && !data.anyactive) {
                // check to make sure that this is not prevented
                if ($special.drag.hijack(drop.event, "dropstart", dd, x, data.elem)[0] !== false) {
                  data.active[x] = 1;
                  data.anyactive += 1;
                }
                // if false, it is not a winner
                else
                  data.winner = 0;
              }
              // if it is still a winner
              if (data.winner)
                ia.drop.push(data.elem);
            }
            // losers...
            else if (data.active[x] && data.anyactive == 1) {
              // former winner... dropend
              $special.drag.hijack(drop.event, "dropend", dd, x, data.elem);
              data.active[x] = 0;
              data.anyactive -= 1;
            }
          } while (++i < len); // loop
        } while (++x < end) // loop
        // check if the mouse is still moving or is idle
        if (drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY)
          delete drop.timer; // idle, don't recurse
        else  // recurse
          drop.timer = setTimeout(function () {
            drop.tolerate(dd);
          }, drop.delay);
        // remember event, to compare idleness
        drop.last = drop.event;
      }

    };

// share the same special event configuration with related events...
  $special.dropinit = $special.dropstart = $special.dropend = drop;

})(jQuery); // confine scope	
